/*
 * ExampleSSLServer.c
 *
 * This program is an OpenSSL server that sits and listens for an incoming
 * SSL connection.  Once a connection is made, the server reads a "message"
 * (defined as a stream terminated by <LF>) from the client, and sends
 * back a modified response to the client before closing the connection.
 * The code to perform the SSL stuff is (as much as possible) all contained
 * in "main" - the use of other functions is limited to just displaying
 * information, so that you should only need to read the "main" code to
 * see what order things are done.
 *
 * Environment variables:
 *   servercert - assumed to point at a .PEM file containing a certificate
 *                chain to be used by this server to authenticate itself
 *   privatekey - assumed to point at a .PEM file containing a private
 *                key for the server certificate
 *   serverpwd  - the password for the private key
 *
 * Tested on
 * Tru64 UNIX V5.1, OpenSSL 0.9.6c
 * OpenVMS V7.2-1, OpenSSL 0.9.5a with "CC/POINTER=64"
 *
 * Nick Hudson, May 2002
 */
#include <openssl/bio.h>
#include <openssl/ssl.h>
#include <openssl/rand.h>
#include <openssl/err.h>

#include <ctype.h>

#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <netdb.h>

char *randbuf = 
"this isn't a very good way to seed the PRNG but will suffice"
"this isn't a very good way to seed the PRNG but will suffice"
"this isn't a very good way to seed the PRNG but will suffice"
"this isn't a very good way to seed the PRNG but will suffice"
"this isn't a very good way to seed the PRNG but will suffice";

int isTracing = 0;

/* Print SSL errors and exit*/
void error_exit(char *string)
{
  printf("%s : \n",string);
  ERR_print_errors_fp(stdout);
  exit(0);
}

/*
 * If "doTrace" is non-zero, then send the message to the stdout
 */
void trace(int doTrace, const char *message) 
{
  if (doTrace) {
    printf("%s\n",message);
  }
}
/*
 * This method looks at the run-time environment in an attempt to check
 * if things are set up correctly for the program to run.  It reports
 * areas of concern but does not abort
 */
void checkEnvironment()
{
  // Check whether they've specified a location for the server certificate
  if (getenv("servercert") == NULL) {
    trace(1,
"\nThe environment variable 'servercert' is not defined, which means that");
    trace(1,
"this program has no certificate chain to send to a client when using a");
    trace(1,
"cipher suite that requires authentication.  Set the environment variable");
    trace(1,
"'servercert' to point to a suitable file if you need to use one.\n");
    }
  
  // Is there an environment variable which we can use for the password?
  if (getenv("serverpwd") == NULL) {
    trace(1,
"\nThe environment variable 'serverpwd' is not defined, which means");
    trace(1,
"that a password be required for a certificate's private key, the SSL");
    trace(1,
"library will prompt you for it at the terminal.  Set the environment");
    trace(1,
"variable 'serverpwd' to a password to override this behaviour.\n");
    }

  // Is there an environment variable which we can use for the private key?
  if (getenv("privatekey") == NULL) {
    trace(1,
"\nThe environment variable 'privatekey' is not defined, which means");
    trace(1,
"that it may not be possible to use server certificates for cipher suites");
    trace(1,
"that require server authentication.  Set the environment variable");
    trace(1,
"'privatekey' to specify a PEM format private key file.\n");
    }
  
}


void usage()
{
  trace(1,"\nUsage: ExampleSSLServer <port> <ciphers> <protocol> [trace]");
  trace(1," port        : port number to use");
  trace(1," ciphers     : types of cipher suites to propose :-");
  trace(1,"               'c' : suites that use server-cert");
  trace(1,"               'a' : anonymous (no server-cert)");
  trace(1,"               'n' : suites with no encryption");
  trace(1," protocol    : '1' '2' or '3' or '4' where :-");
  trace(1,"               '1' means TLSv1");
  trace(1,"               '2' means TLSv1.1");
  trace(1,"               '3' means TLSv1.2");
  trace(1,"               '4' means SSLv3");
  trace(1,"               '5' means SSLv23");
  trace(1," trace       : (optional) tracing to enable :-");
  trace(1,"               't' : turn on program tracing");
}

/*
 * This callback hands back the password to be used during decryption,
 * and returns a value derived from the environment variable "serverpwd".
 *
 * buf      : the function will write the password into this buffer
 * size     : the size of "buf"
 * rwflag   : indicates whether the callback is being used for reading/
 *            decryption (0) or writing/encryption (1)
 * userdata : pointer to area which can be used by the application

 */
static int passwd_cb(char *buf,int size,
		     int rwflag,void *userdata)
{

  char *password;
  int password_length;
  
  trace(isTracing,"password_cb has been called");
  
  password = getenv("serverpwd");
  password_length = strlen(password);

  if ((password_length + 1) > size) {
    trace(1,"Password specified by environment variable is too big");
    return 0;
  }
  
  strcpy(buf,password);
  return password_length;
  
}
/*
 * The following function was generated using the openssl utility, using
 * the command : "openssl dhparam -dsaparam -C 512"
 */
DH *get_dh512()
{
  static unsigned char dh512_p[]={
    0x9B,0x9A,0x2B,0x34,0xDA,0x9A,0x55,0x53,0x47,0xDB,0xCF,0xB4,
    0x26,0xAA,0x4D,0xFD,0x01,0x91,0x4A,0x19,0xE0,0x90,0xFA,0x6B,
    0x99,0xD6,0xE2,0x78,0xF3,0x31,0xD3,0x93,0x9B,0x7B,0xE1,0x65,
    0x57,0xFD,0x4D,0x2C,0x4E,0x17,0xE1,0xAC,0x30,0xB7,0xD0,0xA6,
    0x80,0x13,0xEE,0x37,0xD1,0x83,0xCD,0x5F,0x88,0x38,0x79,0x9C,
    0xFD,0xCE,0x85,0xED,
  };
  static unsigned char dh512_g[]={
    0x8B,0x17,0x22,0x46,0x30,0xAD,0xE5,0x06,0x42,0x60,0x15,0x79,
    0xA2,0x2F,0xD9,0xAA,0x7B,0xD7,0x8A,0x6F,0x39,0xEB,0x13,0x38,
    0x54,0xA6,0xBE,0xAD,0xC6,0x6A,0x17,0x95,0xBE,0x8B,0x29,0xE0,
    0x60,0x14,0x72,0xC9,0x5C,0x84,0x5D,0xD6,0x8B,0x57,0xD9,0x9D,
    0x08,0x60,0x73,0x78,0x3F,0xDD,0x26,0x2C,0x40,0x63,0xCF,0xE0,
    0xDC,0x58,0x7A,0x9C,
  };
  DH *dh;
  
  if ((dh=DH_new()) == NULL) return(NULL);
  dh->p=BN_bin2bn(dh512_p,sizeof(dh512_p),NULL);
  dh->g=BN_bin2bn(dh512_g,sizeof(dh512_g),NULL);
  if ((dh->p == NULL) || (dh->g == NULL))
    { DH_free(dh); return(NULL); }
  dh->length = 160;
  return(dh);
}

/*
 * Display all the ciphers available for a specific SSL structure
 */
void enumerate_ciphers(SSL *ssl)
{
  char tracebuf[512];
  
  int index = 0;
  const char *next = NULL;
  do {
    next = SSL_get_cipher_list(ssl,index);
    if (next != NULL) {
      sprintf(tracebuf,"  %s",next);
      trace(1,tracebuf);
      index++;
    }
  }
  while (next != NULL);  
}

/*
 * Set up a socket to listen on a specific port, and then wait for the 
 * first incoming connection.  Once a connection appears, the function
 * returns the socket id for the new connection.
 */
int getNewConnection(int portNum)
{
  int ListenSock, newConnection;
  int stat;
  int val = 1;
  struct sockaddr_in SA;
  
  ListenSock = socket(AF_INET,SOCK_STREAM,0);
  if (ListenSock < 0) {
    error_exit("Call to create socket failed");
  }

  memset(&SA,0,sizeof(SA));
  
  // Fill in the name & address for the listen socket
  SA.sin_family = AF_INET;
  SA.sin_port = htons(portNum);
  SA.sin_addr.s_addr = INADDR_ANY;

  setsockopt(ListenSock,SOL_SOCKET,SO_REUSEADDR,&val,sizeof(val));
  
  stat = bind(ListenSock,
		 (struct sockaddr *)&SA,
		 sizeof(SA));
  
  if (stat == -1) {
    error_exit("Call to bind failed");
  }
  
  stat = listen(ListenSock,5);  // 5 is the "backlog"
  if (stat == -1) {
    error_exit("Call to listen failed");
  }
  
  newConnection = accept(ListenSock,0,0);
  
  return newConnection;
  
}

void main(int argc, char *argv[]) 
{
  // parse command line args
  int portNumber;
  int enableAnonSuites = 0;
  int enableCertSuites = 0;
  int enableNullSuites = 0;
  int stat;
  char tracebuf[512];
  char buff[512];
  char ciphers[512];
  char clientMessage[512];
  int clientSocket;
  int totalBytes;
  int messageComplete;
  int i;
  
  SSL_METHOD *meth = NULL;
  SSL_CTX *ctx;
  SSL *ssl;
  BIO *sbio;

  if (argc < 4) {
    usage();
    return;
  }
  
  portNumber = atoi(argv[1]);
  enableAnonSuites = (strchr(argv[2],'a') != NULL);
  enableCertSuites = (strchr(argv[2],'c') != NULL);
  enableNullSuites = (strchr(argv[2],'n') != NULL);
  
  switch (atoi(argv[3]))
  {
  case 1 : meth = TLSv1_method(); break;
  case 2 : meth = TLSv1_1_method(); break;
  case 3 : meth = TLSv1_2_method(); break;
  case 4 : meth = SSLv3_method(); break;
  case 5 : meth = SSLv23_method(); break;
  default : usage(); return;
  }
  // trace argument is optional, but there's only one kind of
  // tracing we support
  if (argc > 4) {
    isTracing = 1;
  }

  checkEnvironment();

  trace(1,"Configuring SSL connection...");

  // Register all the libssl error strings
  trace(isTracing,"Calling SSL_load_error_strings()...");
  SSL_load_error_strings();
  
  // Register all available ciphers and digests.  The documentation
  // says you can disregard the return status
  trace(isTracing,"Calling SSL_library_init()...");
  SSL_library_init();

  // Seed the pseudo random number generator (PRNG).  If you don't do this
  // then if you're lucky, SSL will complain "PRNG not seeded".  Alternatively
  // things may just fail.
  trace(isTracing,"Calling RAND_seed()...");
  RAND_seed(randbuf,strlen(randbuf));
  
  

  // Now create the SSL context from which we will be able to create
  // SSL sessions
  trace(isTracing,"Calling SSL_CTX_new()...");
  ctx = SSL_CTX_new(meth);

  if (ctx == NULL) {
    error_exit("call to SSL_CTX_new() returned NULL");
  }

  // Tell SSL that we don't want to request client certificates for
  // verification
  trace(isTracing,"SSL_CTX_set_verify()...");
  SSL_CTX_set_verify(ctx, 
		     SSL_VERIFY_NONE, 
		     NULL);

  // Now tell SSL where its server certificate chain is (assuming the
  // user has told us)
  if (getenv("servercert") != NULL) {
    trace(isTracing,"Calling SSL_CTX_use_certificate_chain_file()...");
    stat = SSL_CTX_use_certificate_chain_file(ctx,getenv("servercert"));
    if (stat != 1) {
      error_exit("SSL_CTX_use_certificate_chain_file failed");
    }
  }
  
  
  // If the user has specified a password, tell SSL the address of
  // a callback routine which will return it
  if (getenv("serverpwd") != NULL) {
    trace(isTracing,"Calling SSL_CTX_set_default_passwd_cb()...");
    SSL_CTX_set_default_passwd_cb(ctx,passwd_cb);
  }

  if (getenv("privatekey") != NULL) {
    trace(isTracing,"Calling SSL_CTX_use_PrivateKey_file()...");
    stat = SSL_CTX_use_PrivateKey_file(ctx,
				       getenv("privatekey"),
				       SSL_FILETYPE_PEM);
  }
  


  // Load the ciphers that they've asked for.
  // It could be inferred from the documentation for ciphers(1) that you 
  // can call SSL_CTX_set_cipher_list multiple times to build up a list 
  // of ciphers.  But that isn't how it works; rather you build up a list 
  // of ciphers in a string, and then pass that in a single call:

  // First of all disable any ciphers we've got by default
  ciphers[0] = 0;
  strcat(ciphers,"-ALL");
  
  // If they want cipher suites that require certificates, add them in
  if (enableCertSuites) {
    strcat(ciphers,":ALL:-aNULL");
  }

  // If they want the null ones, add them in
  if (enableNullSuites) {
    strcat(ciphers,":NULL");
  }

  // If they want the anonymous ones, add them in
  if (enableAnonSuites) {
    strcat(ciphers,":aNULL");
  }

  sprintf(tracebuf,"Calling SSL_CTX_set_cipher_list(\"%s\")...",ciphers);
  trace(isTracing,tracebuf);
  
  
  stat = SSL_CTX_set_cipher_list(ctx,ciphers);
  if (stat == 0) {
    error_exit("SSL_CTX_set_cipher_list() failed");
  }

  // Provide DH key information (if you don't do this, then ciphers that
  // require DH key exchange won't be used, even if they are in the list of
  // ciphers for the ctx)
  trace(isTracing,"Calling SSL_CTX_set_tmp_dh()...");
  stat = SSL_CTX_set_tmp_dh(ctx,get_dh512());

  ssl = SSL_new(ctx);
  if (isTracing) {
    trace(1,"Enabled cipher suites are :");
    enumerate_ciphers(ssl);
  }
  
  // Wait for an incoming connection
  trace(1,"\n...Now waiting for a client to connect");
  clientSocket = getNewConnection(portNumber);

  trace(1,"Connection received");
  
  // Generate a BIO wrapper for the TCP socket
  trace(isTracing,"Calling BIO_new_socket()...");
  sbio = BIO_new_socket(clientSocket,BIO_NOCLOSE);
  if (sbio == NULL) {
    error_exit("BIO_new_socket() failed");
  }
  
  // Connect up our SSL session with the socket
  trace(isTracing,"Calling SSL_set_bio()...");
  SSL_set_bio(ssl,sbio,sbio);
  
  trace(isTracing,"Calling SSL_accept()...");
  stat = SSL_accept(ssl);
  if (stat <= 0) {
    error_exit("SSL_accept failed");
  }
  
  // Now read stuff from the client, assuming that it will be a
  // message terminated by EOL
  totalBytes = 0;
  messageComplete = 0;
  do {
    trace(isTracing,"Calling SSL_read()...");
    stat = SSL_read(ssl,
		    &buff[totalBytes],
		    (sizeof(buff) - totalBytes));
    if (stat <= 0) {
      error_exit("SSL_read failed");
    }

    // Look for an EOL
    for (i=0; i<stat; i++) {
      if (buff[(i+totalBytes)] == '\n') {
	
	// Stick a NULL terminator just beyond it
	buff[(i + totalBytes + 1)] = 0;
	messageComplete = 1;
	break;
      }
    }
    totalBytes += stat;
  }
  while (messageComplete == 0);

  sprintf(tracebuf,"The client sent -->%s<--", buff);
  trace(1,tracebuf);
  
  sprintf(clientMessage,"OpenSSL server says thank-you for saying : %s",buff);
  
  trace(isTracing,"Calling SSL_write()...");
  stat = SSL_write(ssl,clientMessage,strlen(clientMessage));

  if (stat <= 0) {    
    error_exit("SSL_write failed");
  }

  if (stat != strlen(clientMessage)) {
    error_exit("Incomplete SSL write");
  }
  
  trace(isTracing,"Shutting down connection...");
  stat = SSL_shutdown(ssl);
  if (stat < 0) {
    error_exit("SSL_shutdown failed");
  }
  if (stat == 0) {
    // According to docs at the OpenSSL website, this means that the shutdown
    // has not yet finished, and we must call SSL_shutdown again..
    stat = SSL_shutdown(ssl);
    if (stat <= 0) {
      error_exit("SSL_shutdown (2nd time) failed");
    }
  }
  
  
  SSL_free(ssl);
  SSL_CTX_free(ctx);
  close(clientSocket);
  
  trace(1,"\nProgam terminating");
}

